// ---------------------------------------------------------------------------------------------------------------------
//   Copyright (c) SRT Solutions 2009. All rights reserved.
// ---------------------------------------------------------------------------------------------------------------------

namespace SRTSolutions.Elevate
{
    using global::System;
    using global::System.Collections;
    using global::System.Collections.Generic;
    using global::System.Linq;

    /// <summary>
    /// An implementation of a MultiMap, that is a Map which has many values
    /// for a given key.
    /// </summary>
    public class MultiMap<TKey,TValue> : ILookup<TKey, TValue>, IEnumerable<KeyValuePair<TKey, TValue>>
    {

        Dictionary<TKey, IList<TValue>> multimap = new Dictionary<TKey, IList<TValue>>();

        /// <summary>
        /// Initializes a new instance of the <see cref="MultiMap&lt;TKey, TValue&gt;"/> class.
        /// </summary>
        public MultiMap() 
        { }

        #region IDictionary<TKey,TValue> like Members

        /// <summary>
        /// Adds the specified key and value to the dictionary.
        /// </summary>
        /// <param name="key">The key of the element to add.</param>
        /// <param name="value">The value of the element to add.</param>
        public void Add(TKey key, TValue value)
        {
            if (!multimap.ContainsKey(key))
                multimap.Add(key, new List<TValue>());
            multimap[key].Add(value);
        }

        /// <summary>
        /// Determines whether the MultiMap contains the specified key.
        /// </summary>
        /// <param name="key">The key to locate in the MultiMap.</param>
        /// <returns>
        /// 	<c>true</c> if the MultiMap contains the specified key; otherwise, <c>false</c>.
        /// </returns>
        public bool ContainsKey(TKey key)
        {
            return multimap.ContainsKey(key);
        }

        /// <summary>
        /// Gets a collection containing the keys in the MultiMap
        /// </summary>
        /// <value>The keys.</value>
        public ICollection<TKey> Keys
        {
            get { return multimap.Keys; }
        }

        /// <summary>
        /// Removes the value with the specified key from the MultiMap.
        /// </summary>
        /// <param name="key">The key.</param>
        /// <returns></returns>
        public bool Remove(TKey key)
        {
            bool found = multimap.Remove(key);
            return found;
        }

        /// <summary>
        /// Gets the value associated with the specified key.
        /// </summary>
        /// <param name="key">The key of the value to get.</param>
        /// <param name="value">When this method returns, contains the value associated with the specified key, if the key is found; otherwise, the default value for the type of value parameter.</param>
        /// <returns></returns>
        public bool TryGetValue(TKey key, out IList<TValue> value)
        {
            return multimap.TryGetValue(key, out value);
        }

        /// <summary>
        /// Gets a collection containing the values in the MultiMap
        /// </summary>
        /// <value>The values.</value>
        public IList<TValue> Values
        {
            get { return multimap.Values.SelectMany(x=>x).ToList(); }
        }

        /// <summary>
        /// Gets the <see cref="System.Collections.Generic.IList&lt;TValue&gt;"/> with the specified key.
        /// </summary>
        /// <value></value>
        public IList<TValue> this[TKey key]
        {
            get
            {
                return multimap[key];
            }
        }

        #endregion
 
        /// <summary>
        /// Clears this instance.
        /// </summary>
        public void Clear()
        {
            this.multimap.Clear();
        }

        /// <summary>
        /// Gets the number of key/value collection pairs in the <see cref="T:System.Linq.ILookup`2"/>.
        /// </summary>
        /// <value></value>
        /// <returns>
        /// The number of key/value collection pairs in the <see cref="T:System.Linq.ILookup`2"/>.
        /// </returns>
        public int Count
        {
            get { return this.multimap.Count; }
        }

        /// <summary>
        /// Gets a value indicating whether this instance is read only.
        /// </summary>
        /// <value>
        /// 	<c>true</c> if this instance is read only; otherwise, <c>false</c>.
        /// </value>
        public bool IsReadOnly
        {
            get { return (this.multimap as IList).IsReadOnly; }
        }

        #region IEnumerable Members

        /// <summary>
        /// Returns an enumerator that iterates through a collection.
        /// </summary>
        /// <returns>
        /// An <see cref="T:System.Collections.IEnumerator"/> object that can be used to iterate through the collection.
        /// </returns>
        IEnumerator IEnumerable.GetEnumerator()
        {
            return this.GetEnumerator();
        }

        #endregion

        #region ILookup<TKey,TValue> Members

        /// <summary>
        /// Determines whether a specified key exists in the <see cref="T:System.Linq.ILookup`2"/>.
        /// </summary>
        /// <param name="key">The key to search for in the <see cref="T:System.Linq.ILookup`2"/>.</param>
        /// <returns>
        /// true if <paramref name="key"/> is in the <see cref="T:System.Linq.ILookup`2"/>; otherwise, false.
        /// </returns>
        public bool Contains(TKey key)
        {
            return this.ContainsKey(key);
        }

        /// <summary>
        /// Gets the <see cref="System.Collections.Generic.IEnumerable&lt;TValue&gt;"/> with the specified key.
        /// </summary>
        /// <value></value>
        IEnumerable<TValue> ILookup<TKey, TValue>.this[TKey key]
        {
            get { return this[key]; }
        }

        #endregion

        #region IEnumerable<IGrouping<TKey,TValue>> Members

        /// <summary>
        /// Returns an enumerator that iterates through the collection.
        /// </summary>
        /// <returns>
        /// A <see cref="T:System.Collections.Generic.IEnumerator`1"/> that can be used to iterate through the collection.
        /// </returns>
        IEnumerator<IGrouping<TKey, TValue>> IEnumerable<IGrouping<TKey, TValue>>.GetEnumerator()
        {
            foreach (var key in this.multimap)
            {                
                    yield return new MultiMapGroup(key.Key, key.Value);
            }
        }

        #endregion

        /// <summary>
        /// Returns an enumerator that iterates through the collection.
        /// </summary>
        /// <returns>
        /// A <see cref="T:System.Collections.Generic.IEnumerator`1"/> that can be used to iterate through the collection.
        /// </returns>
        public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
        {
            foreach (var key in this.multimap)
            {
            	foreach (var value in key.Value)
                {
                    yield return new KeyValuePair<TKey, TValue>(key.Key, value);
                }
            }
        }

        private class MultiMapGroup : IGrouping<TKey, TValue>
        {
            #region IGrouping<TKey,TValue> Members

            public TKey Key
            {
                get;
                private set;
            }

            #endregion
            
            IList<TValue> values;
            
            /// <summary>
            /// Initializes a new instance of the MultiMapGroup class.
            /// </summary>
            public MultiMapGroup(TKey key, IList<TValue> value)
            {
                Key = key;
                values = value;
            }

            #region IEnumerable<TValue> Members

            public IEnumerator<TValue> GetEnumerator()
            {
                return values.GetEnumerator();
            }

            #endregion

            #region IEnumerable Members

            IEnumerator IEnumerable.GetEnumerator()
            {
                return GetEnumerator();
            }

            #endregion
        }

    }
}
